/*
 * Copyright 2015 CPE Lyon - IRC 2013/2016 - Cours de programmation réseau - Groupe H
 *
 * This file is part of BOTT.
 *
 * BOTT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * BOTT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with BOTT. If not, see http://www.gnu.org/licenses/.
 */

/*
 * Fonctions dédiées au client de messagerie.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include "client.h"
#include "protocole.h"
#include "fonctions.h"
#include "fonctions-client.h"

/* Se connecter à un serveur */
int doConnect(int socketDesc, struct sockaddr_in *serverAddr, char *username) {

    struct timeval tv;
    int n;
    fd_set fds;
    bimp_msg receivedData;
    socklen_t addrLen;

    /* Structure à envoyer */
    bimp_msg sendData;
    sendData.idCommand = 1;
    sendData.idUser = 0;
    sendData.idChat = 0;
    strcpy(sendData.data, username);
    if (sendto(socketDesc, &sendData, sizeof(sendData), 0, (struct sockaddr *) serverAddr, 16+0*sizeof(&serverAddr)) == -1) {
        perror("Socket send error");
        return 0;
    }

    FD_ZERO(&fds);
    /* Définition du timeout */
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    /* Attente de la réponse */
    FD_SET(socketDesc, &fds);
    n = select(socketDesc + 1, &fds, NULL, NULL, &tv);

    if (n == 0) {
        fprintf(stderr, "Connection timeout reached\n");
        return 0;
    } else if (n == -1) {
        perror("Select error");
        return 0;
    } else {
        if (FD_ISSET(socketDesc, &fds)) {
            addrLen = sizeof(serverAddr);
            if ((recvfrom(socketDesc, &receivedData, sizeof(receivedData), 0, (struct sockaddr *) serverAddr, &addrLen)) == -1) {
                perror("Socket receive error");
            } else if (receivedData.idCommand == 105) {
                return receivedData.idUser;
            } else {
                fprintf(stderr, "Connection error: %s\n", receivedData.data);
                return 0;
            }
        }
    }

    return 0;
}

/* Rejoindre un salon */
room doJoin(int socketDesc, struct sockaddr_in *serverAddr, char *roomName, int idUser, room *listRooms, room currentRoom) {
    int receivedLen;
    socklen_t addrLen;

    /* Structure à envoyer */
    bimp_msg sendData;
    sendData.idCommand = 2;
    sendData.idUser = idUser;
    sendData.idChat = 0;
    strcpy(sendData.data, roomName);

    if (sendto(socketDesc, &sendData, sizeof(sendData), 0, (struct sockaddr *) serverAddr, 16+0*sizeof(&serverAddr)) == -1) {
        perror("Socket send error");
    }
    /* Réponse du serveur */
    addrLen = sizeof(serverAddr);
    bimp_msg receivedData;
    receivedLen = recvfrom(socketDesc, &receivedData, sizeof receivedData, 0, (struct sockaddr *) &serverAddr, &addrLen);

    if (receivedLen <= 0) {
        perror("Data reception error");
        return currentRoom;
    }

    if (receivedData.idCommand == 106) {
        for (int i = 0; i < MAX; i++) {
            if (listRooms[i].idRoom == 0) {
                /* On ajoute le salon à la liste */
                listRooms[i].idRoom = receivedData.idChat;
                strcpy(listRooms[i].roomName, roomName);
                for (int j = 0; j < MAX_MESSAGES_SAVED; j++) {
                    strcpy(listRooms[i].listMessages[j], "");
                }
                return listRooms[i];
            }
        }
    } else if (receivedData.idCommand == 104) {
        fprintf(stderr, "%s\e[1m%s\n", KRED, receivedData.data);
        printf("%s\n", KBLU);
    }

    return currentRoom;
}

/* Envoyer un message sur un salon */
void doSay(int socketDesc, struct sockaddr_in *serverAddr, int idUser, room *listRooms, room currentRoom, char *message) {
    bimp_msg sendData;
    sendData.idCommand = 3;
    sendData.idUser = idUser;
    sendData.idChat = currentRoom.idRoom;
    strcpy(sendData.data, message);
    if (sendto(socketDesc, &sendData, sizeof(sendData), 0, (struct sockaddr *) serverAddr, 16+0*sizeof(&serverAddr)) == -1) {
        perror("Socket send error");
    }
    treatTell(sendData, listRooms);
}

/* Quitter un salon */
int doLeave(int socketDesc, struct sockaddr_in *serverAddr, int idUser, room *listRooms, room leavingRoom) {
    int receivedLen;
    int leavingResult = 0;
    socklen_t addrLen;

    /* Structure à envoyer */
    bimp_msg sendData;
    sendData.idCommand = 4;
    sendData.idUser = idUser;
    sendData.idChat = leavingRoom.idRoom;
    strcpy(sendData.data, "");

    if (sendto(socketDesc, &sendData, sizeof(sendData), 0, (struct sockaddr *) serverAddr, 16+0*sizeof(&serverAddr)) == -1) {
        perror("Socket send error");
    }
    /* Réponse du serveur */
    addrLen = sizeof(serverAddr);
    bimp_msg receivedData;
    receivedLen = recvfrom(socketDesc, &receivedData, sizeof receivedData, 0, (struct sockaddr *) &serverAddr, &addrLen);

    if (receivedLen <= 0) {
        perror("Data reception error");
        return 0;
    }

    if (receivedData.idCommand == 107) {
        for (int i = 0; i < MAX; i++) {
            if (listRooms[i].idRoom == leavingRoom.idRoom) {
                /* On supprime le salon de la liste */
                listRooms[i].idRoom = 0;
                leavingResult = 1;
                break;
            }
        }
    } else if (receivedData.idCommand == 104) {
        fprintf(stderr, "%s\e[1m%s\n", KRED, receivedData.data);
        printf("%s\n", KBLU);
    }

    return leavingResult;
}

/* Se déconnecter d'un serveur */
void doDisconnect(int socketDesc, struct sockaddr_in *serverAddr, int idUser ) {
    /* Structure à envoyer */
    bimp_msg sendData;
    sendData.idCommand = 5;
    sendData.idUser = idUser;
    sendData.idChat = 0;
    strcpy(sendData.data, "");
    if (sendto(socketDesc, &sendData, sizeof(sendData), 0, (struct sockaddr *) serverAddr, 16+0*sizeof(&serverAddr)) == -1) {
        perror("Socket send error");
    }
    close(socketDesc);
    printf("Successfully disconnected\n");
}

/* Fonction permettant la notification "ALIVE" du client */
int doAlive(int socketDesc, struct sockaddr_in *sendAddr, int idUser) {
    int alive = 1;
    bimp_msg sendData;
    sendData.idCommand = 6;
    sendData.idUser = idUser;
    sendData.idChat = 0;
    strcpy(sendData.data, "");
    if (sendto(socketDesc, &sendData, sizeof(sendData), 0, (struct sockaddr *) sendAddr, sizeof(*sendAddr)) == -1) {
        alive = 0;
	perror("Socket send error");
    }
    return alive;
}

/* Changer de salon */
room getRoomByName(char *newRoom, room *listRooms) {
    room roomError;
    roomError.idRoom = 0;
    strcpy(roomError.roomName, "");
    for (int i = 0; i < MAX; i++) {
        if (strcmp(listRooms[i].roomName, newRoom) == 0) {
            return listRooms[i];
        }
    }
    return roomError;
}

/* Mettre à jour l'affichage */
void displayChat(room *listRooms, room currentRoom) {
    printf("\033[H\033[2J");
    fflush(stdout);
    setBackground("40");
    int noChat = 1;
    /* On affiche les chats */
    for (int i= 0; i < MAX; i++) {
        if (listRooms[i].idRoom != 0) {
            if (listRooms[i].idRoom != currentRoom.idRoom) {
                printf("\e[2m%s%s[ %s ]", KWHT, BBLU, listRooms[i].roomName);
            } else {
                printf("\e[1m%s%s[ %s ]", KWHT, BBLU, listRooms[i].roomName);
            }
            noChat = 0;
        }
    }
    if (noChat) {
        printf("%s%s[ No chat joined ]", KWHT, BBLU);
    }
    printf("\n");
    printf("\e[0m");
    printf("%s\e[1m\n", KBLU);
    /* On affiche les messages */
    for (int i= 0; i < MAX; i++) {
        if (listRooms[i].idRoom == currentRoom.idRoom) {
            for (int j = 0; j < MAX_MESSAGES_SAVED; j++) {
                if (strlen(listRooms[i].listMessages[j]) > 0) {
                    printf("%s\n", listRooms[i].listMessages[j]);
                }
            }
        }
    }
}

/* Traiter un message reçu */
void treatTell(bimp_msg receivedData, room *listRooms) {
    int full = 1;
    char message[MAX_MESSAGE_LENGTH];
    /*
    socklen_t addrLen;
    int16_t receivedUserId = receivedData.idUser;
    */
    char receivedMessage[MAX_MESSAGE_LENGTH];
    strcpy(receivedMessage, receivedData.data);
    /* Ajout du message */
    for (int i = 0; i < MAX; i++) {
        if (listRooms[i].idRoom == receivedData.idChat) {
            for (int j = 0; j < MAX_MESSAGES_SAVED; j++) {
                if (strlen(listRooms[i].listMessages[j]) == 0) {
                    // Tentative de récupération du pseudonyme (non fonctionnelle)
                    /*
                    bimp_msg sendDataNick, receivedDataNick;
                    sendDataNick.idCommand = 8;
                    sendDataNick.idUser = receivedData.idUser;
                    sendDataNick.idChat = 0;
                    strcpy(sendDataNick.data, "");
                    if (sendto(socketDesc, &sendDataNick, sizeof(sendDataNick), 0, (struct sockaddr *) serverAddr, 16+0*sizeof(&serverAddr)) == -1) {
                        perror("Socket send error");
                    }
                    if (recvfrom(socketDesc, &receivedDataNick, sizeof receivedDataNick, 0, (struct sockaddr *) &serverAddr, &addrLen) != -1) {
                        strcpy(message, "<");
                        strcat(message, receivedDataNick.data);
                        strcat(message, "> ");
                        strcat(message, receivedData.data);
                        strcpy(listRooms[i].listMessages[j], message);
                        full = 0;
                    } else {
                        perror("Data reception error");
                        full = 0;
                    }
                    break;
                    */
                    strcpy(message, "<");
                    sprintf(message + 1, "%d", receivedData.idUser);
                    strcat(message, "> ");
                    strcat(message, receivedMessage);
                    strcpy(listRooms[i].listMessages[j], message);
                    full = 0;
                    break;
                }
            }
            if (!full) {
                break;
            }
        }
    }
    /* Cas où le nombre maximum de messages a été atteint */
    if (full) {
        for (int i = 0; i < MAX; i++) {
            if (listRooms[i].idRoom == receivedData.idChat) {
                for (int j = 0; j < MAX_MESSAGES_SAVED - 1; j++) {
                    strcpy(listRooms[i].listMessages[j], listRooms[i].listMessages[j + 1]);
                }
                break;
            }
        }
        for (int i = 0; i < MAX; i++) {
            if (listRooms[i].idRoom == receivedData.idChat) {
                // Tentative de récupération du pseudonyme (non fonctionnelle)
                /*
                bimp_msg sendData;
                sendData.idCommand = 8;
                sendData.idUser = receivedUserId;
                sendData.idChat = 0;
                strcpy(sendData.data, "");
                if (sendto(socketDesc, &sendData, sizeof(sendData), 0, (struct sockaddr *) serverAddr, 16+0*sizeof(&serverAddr)) == -1) {
                    perror("Socket send error");
                }
                if (recvfrom(socketDesc, &receivedData, sizeof receivedData, 0, (struct sockaddr *) &serverAddr, &addrLen) != -1) {
                    strcpy(message, "<");
                    strcat(message, receivedData.data);
                    strcat(message, "> ");
                    strcat(message, receivedMessage);
                    strcpy(listRooms[i].listMessages[MAX_MESSAGES_SAVED - 1], message);
                } else {
                    perror("Data reception error");
                }
                */
                strcpy(message, "<");
                sprintf(message + 1, "%d", receivedData.idUser);
                //strcat(message, "USERNAME");
                strcat(message, "> ");
                strcat(message, receivedMessage);
                strcpy(listRooms[i].listMessages[MAX_MESSAGES_SAVED - 1], message);
            }
        }
    }
}

/* Vérifier si le serveur répond */
void *checkIfAlive(void* transmitData) {
    aliveData data = (*(aliveData *)transmitData);

    do {
        sleep(5);
    } while (doAlive(data.socketDesc, &data.serverAddr, data.idUser));

    pthread_exit(NULL);
}